package inabit.inference;

import inabit.base.RuleBase;
import inabit.base.WorkingMemory;
import inabit.data.FireableRule;
import inabit.data.Rule;
import inabit.data.expression.ConsequentExpression;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
/**Executes the forward chaining process to infer the most of the working memory state based on initial data
 * @author Rafael Rocha*/
public abstract class ForwardEngine extends InferenceEngine {

	private DefaultTreeModel traceTreeModel;
	private InferenceNode root;

	public ForwardEngine() {
		traceTreeModel = new DefaultTreeModel(root);
	}
	/**Returns the list of rules that can be fired*/
	protected abstract List<FireableRule> conflictSet(RuleBase rb,
			WorkingMemory wm);
	/**Returns the name of the root node*/
	protected abstract String getRootNodeLabel();
	
	/**Returns the name for the inner node at the inference process*/
	protected abstract String getInnerNodeLabel(Rule r);
	
	/**Returns the name for a consequent node at the inference process*/
	protected abstract String getConsequentString(ConsequentExpression consequent);

	/**Retuns a rule from the conflict set to be fired*/
	protected abstract FireableRule resolveSet(List<FireableRule> conflictSet,
			WorkingMemory wm);

	private boolean isChild(InferenceNode root, InferenceNode child) {
		Enumeration<InferenceNode> e = root.children();
		while (e.hasMoreElements()) {
			if (e.nextElement().equals(child)) {
				return true;
			}
		}
		return false;
	}
	
	/**Realizes the inference process filling the trace tree*/
	public void inference(RuleBase rb, WorkingMemory wm) throws Exception {
		rb.resetRules();
		root = new InferenceNode(getRootNodeLabel(), false);
		traceTreeModel.setRoot(root);
		List<FireableRule> conflictSet = new ArrayList<FireableRule>();
		conflictSet.addAll(conflictSet(rb, wm));
		for (int i = 0; i < conflictSet.size(); i++) {
			Rule rule = conflictSet.get(i);
			InferenceNode node = new InferenceNode(getInnerNodeLabel(rule), false);
			if (!isChild(root, node)) {
				traceTreeModel.insertNodeInto(node, root, root.getChildCount());
			}
		}
		root.setVerified(true);
		while (!conflictSet.isEmpty()) {

			FireableRule rule = resolveSet(conflictSet, wm);
			InferenceNode ruleNode = getNode(root, getInnerNodeLabel(rule));
			Double truth = rule.evaluate(wm);
			InferenceNode consequence = null;
			
			if (truth > 0) {
				consequence = new InferenceNode(getConsequentString((ConsequentExpression)rule.getConsequent()), true);
				consequence.setReferencedVariables(((ConsequentExpression)rule.getConsequent()).getTargetVariables(wm));
				traceTreeModel.insertNodeInto(consequence, ruleNode,consequence.getChildCount());
				try {
					rule.fire(wm, truth);
				}catch(Exception e) {
					consequence.setIconsistencyNode(true);
					throw e;
				}
			}
			
			List<FireableRule> oldSet = new ArrayList<FireableRule>();
			oldSet.addAll(conflictSet);
			
			conflictSet.clear();
			conflictSet.addAll(conflictSet(rb, wm));
			
			List<FireableRule> newSet = new ArrayList<FireableRule>();
			newSet.addAll(conflictSet);
			newSet.removeAll(oldSet);
			
			for (int i = 0; i < newSet.size(); i++) {
				InferenceNode newNode = new InferenceNode(getInnerNodeLabel(newSet.get(i)), false);
				if (!isChild(consequence, newNode)) {
					traceTreeModel.insertNodeInto(newNode, consequence,
							consequence.getChildCount());
				}
			}
			if(consequence.getChildCount() == 0) {
				consequence.setVerified(true);
			}
			ruleNode.setVerified(true);
		}
	}

	/**Returns the trace tree representing the inference process' steps*/
	public DefaultTreeModel getTraceTreeModel() {
		return traceTreeModel;
	}
	/**Returns the root of the inference process*/
	public InferenceNode getTraceRoot() {
		return root;
	}
}
